/*
 *    Copyright 2009-2023 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.ryan.builder;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.jdbc.AbstractSQL;
import org.apache.ibatis.jdbc.SQL;
import org.apache.ibatis.ryan.Tools;
import org.apache.ibatis.ryan.utils.Assert;
import org.apache.ibatis.ryan.utils.PageHoder;

public class CountBuilder<T>
        implements Builder<T> {
    public static String param3 = "param3";

    private SQL sql = new SQL();
    private Class<T> resultType;
    private String from;
    private Map<String, Object> params = new HashMap<>();
    private String countFunc;
    private Class<?> join;
    private String joinTable;
    private boolean left;

    public String getSql() {
        return sql.toString();
    }

    private CountBuilder(Class<T> resultType, String from, String countFunc) {
        this.resultType = resultType;
        this.from = (resultType != null) ? Tools.tableName(resultType) : Tools.toUnderLine(from);
        this.countFunc = countFunc;
        init();
    }

    public static <T> CountBuilder<T> build(Class<T> tableType) {
        Assert.checkNotNull(tableType, "tableType can not  be null!");
        CountBuilder<T> criteriaQuery = new CountBuilder<>(tableType, null, null);
        return criteriaQuery;
    }

    public static <T> CountBuilder<T> build(Class<T> tableType, String countFunc) {
        CountBuilder<T> criteriaQuery = new CountBuilder<>(tableType, null, countFunc);
        return criteriaQuery;
    }

    public static <T> CountBuilder<T> build(String table, String countFunc) {
        Assert.checkNotNull(table, "table can not  be null!");
        CountBuilder<T> criteriaQuery = new CountBuilder<>(null, table, countFunc);
        return criteriaQuery;
    }

    public static <T> CountBuilder<T> build(String table) {
        Assert.checkNotNull(table, "table can not  be null!");
        CountBuilder<T> criteriaQuery = new CountBuilder<>(null, table, null);
        return criteriaQuery;
    }

    private void init() {
        this.sql.SELECT(StringUtils.isBlank(this.countFunc) ? " count(1) cnt" : this.countFunc);
        this.sql.FROM(this.from);
    }

    public Class<T> getResultType() {
        return this.resultType;
    }

    public Map<String, Object> getParams() {
        return this.params;
    }

    public CountBuilder<T> whereEq(String propName, Object value) {
        return where(Tools.eq, propName, value);
    }

    public CountBuilder<T> whereNotEq(String propName, Object value) {
        return where(Tools.notEq, propName, value);
    }

    private CountBuilder<T> where(String op, String propName, Object value) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return this;
        }
        String varName = Tools.buildVar(propName, this.params, null, value);
        String colName = Tools.toUnderLine(propName);
        this.sql.WHERE(String.format(op, new Object[] { colName, param3, varName }));
        return this;
    }

    public CountBuilder<T> whereLike(String propName, String value) {
        return where(Tools.like, propName, value);
    }

    public CountBuilder<T> whereLow(String propName, Object value) {
        return where(Tools.low, propName, value);
    }

    public CountBuilder<T> whereLowEq(String propName, Object value) {
        return where(Tools.lowEq, propName, value);
    }

    public CountBuilder<T> whereIn(String propName, List value) {
        Tools.staticWhereIn(propName, param3, value, (AbstractSQL<T>) this.sql, this.params);
        return this;
    }

    public CountBuilder<T> whereNotIn(String propName, List value) {
        Tools.staticWhereNotIn(propName, param3, value, (AbstractSQL) this.sql, this.params);
        return this;
    }

    public CountBuilder<T> whereBig(String propName, Object value) {
        return where(Tools.big, propName, value);
    }

    public CountBuilder<T> whereBigEq(String propName, Object value) {
        return where(Tools.bigEq, propName, value);
    }

    public CountBuilder<T> join(Class<?> tableClass) {
        this.join = tableClass;
        return this;
    }

    public CountBuilder<T> join(Class<?> tableClass, int hitGroupId) {
        String[] cols = Tools.fieldToCols(tableClass, Tools.tableName(tableClass), hitGroupId);
        this.sql.SELECT(cols);
        this.join = tableClass;
        return this;
    }

    public CountBuilder<T> join(Class<?> resultType, String table, int hitGroupId) {
        String[] cols = Tools.fieldToCols(resultType, table, hitGroupId);
        this.sql.SELECT(cols);
        this.joinTable = table;
        return this;
    }

    public CountBuilder<T> leftJoin(Class<?> resultType) {
        this.join = resultType;
        this.left = true;
        return this;
    }

    public CountBuilder<T> leftJoin(Class<?> resultType, int hitGroupId) {
        String[] cols = Tools.fieldToCols(resultType, Tools.tableName(resultType), hitGroupId);
        this.sql.SELECT(cols);
        this.join = resultType;
        this.left = true;
        return this;
    }

    public CountBuilder<T> leftJoin(Class resultType, String table, int hitGroupId) {
        String[] cols = Tools.fieldToCols(resultType, table, hitGroupId);
        this.sql.SELECT(cols);
        this.joinTable = table;
        this.left = true;
        return this;
    }

    public CountBuilder<T> on(String tableOn1, String tableOn2) {
        Assert.check((this.join != null || this.joinTable != null),
                "\n先调用org.lufei.ibatis.builder.CriteriaQuery.on(CriteriaQuery.java)设定join目标表");
        String format = null;

        if (StringUtils.isNotBlank(this.joinTable)) {
            format = String.format(" %s on %s = %s ", new Object[] { Tools.toUnderLine(this.joinTable),
                    Tools.toUnderLine(tableOn1), Tools.toUnderLine(tableOn2) });
        } else {
            format = String.format(" %s on %s = %s ", new Object[] { Tools.toUnderLine(Tools.tableName(this.join)),
                    Tools.toUnderLine(tableOn1), Tools.toUnderLine(tableOn2) });
        }

        if (this.left) {
            this.sql.LEFT_OUTER_JOIN(format);
        } else {
            this.sql.INNER_JOIN(format);
        }

        this.joinTable = null;
        this.join = null;
        this.left = false;
        return this;
    }

    public CountBuilder<T> orderByDesc(String propName) {
        this.sql.ORDER_BY(String.format("%s DESC", new Object[] { Tools.toUnderLine(propName) }));
        return this;
    }

    public CountBuilder<T> orderByAsc(String propName) {
        this.sql.ORDER_BY(String.format("%s ASC", new Object[] { Tools.toUnderLine(propName) }));
        return this;
    }

    public CountBuilder<T> orderBy(String propNameAndOrder) {
        propNameAndOrder = propNameAndOrder.replaceAll("order\\s+by", "");
        this.sql.ORDER_BY(propNameAndOrder);
        return this;
    }

    public CountBuilder<T> setPage(int pageNo, int pageSize) {
        PageHoder.setPageNo(Integer.valueOf(pageNo));
        PageHoder.setPageSize(Integer.valueOf(pageSize));
        return this;
    }

    public <W> CountBuilder<T> whereBean(W whereBean) {
        return whereBean(whereBean, null);
    }

    public CountBuilder<T> or() {
        this.sql.OR();
        return this;
    }

    public <W> CountBuilder<T> whereBean(W whereBean, String table) {
        List<Field> fields = Tools.getInstanceFields(whereBean.getClass());
        if (fields.isEmpty()) {
            return this;
        }

        fields.forEach(field -> {
            Object fieldValue = Tools.getFieldValue(whereBean, field);
            String pre = (table != null) ? table : Tools.tableName(whereBean.getClass());
            whereEq(pre + "." + field.getName(), fieldValue);
        });
        return this;
    }

    public CountBuilder<T> appendWhere(String sqlString) {
        this.sql.WHERE(sqlString);
        return this;
    }
}
